/*
 * Copyright 2012 The AltN8-Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package altn8;

import altn8.filechooser.AlternateFilePopupChooser;
import altn8.filechooser.FileHandler;
import altn8.filematcher.AlternateFileMatcher;
import altn8.filematcher.AlternateFreeRegexFileMatcher;
import altn8.filematcher.AlternateGenericRegexFileMatcher;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ContentIterator;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import org.jetbrains.annotations.NotNull;

import java.util.*;

/**
 * Our main action
 */
public class AlternateFileAction extends AnAction {
    private static VirtualFile getCurrentFile(AnActionEvent e) {
        return PlatformDataKeys.VIRTUAL_FILE.getData(e.getDataContext());
    }

    private static Project getProject(AnActionEvent e) {
        return PlatformDataKeys.PROJECT.getData(e.getDataContext());
    }

    private static Module getModule(AnActionEvent e) {
        return LangDataKeys.MODULE.getData(e.getDataContext());
    }

    private static Editor getEditor(AnActionEvent e) {
        return PlatformDataKeys.EDITOR.getData(e.getDataContext());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void actionPerformed(AnActionEvent e) {
        VirtualFile currentFile = getCurrentFile(e);
        if (currentFile != null) {
            Project currentProject = getProject(e);
            // find these in project
            List<AlternateFileGroup> fileGroups = findFiles(currentFile, currentProject, getModule(e));
            if (fileGroups.isEmpty()) {
                // nothing found
                Editor editor = getEditor(e);
                if (editor != null) { // fix issue 9: can only display hint if there is a editor instance
                    HintManager.getInstance().showInformationHint(editor, "No corresponding file(s) found");
                }
            } else {
                // open these...
                AlternateFilePopupChooser.prompt("Select the file(s) to open", fileGroups, currentProject, new FileHandler() {
                    public void processFile(@NotNull PsiFile psiFile) {
                        psiFile.navigate(true);
                    }
                });
            }
        }
    }

    /**
     * Find all corresponding files.<br>
     * If we found at minimunm one file in module, only module-files are listet. Else project files.
     */
    private List<AlternateFileGroup> findFiles(final VirtualFile currentFile, final Project project, final Module module) {
        AlternateConfiguration configuration = ApplicationManager.getApplication().getComponent(AlternateApplicationComponent.class).getState();

        final Map<String, AlternateFileGroup> projectWorkMap = new HashMap<String, AlternateFileGroup>();
        final Map<String, AlternateFileGroup> moduleWorkMap = new HashMap<String, AlternateFileGroup>();
        final String currentFilename = currentFile.getName();

        // get all fileMatchers
        final List<AlternateFileMatcher> fileMatchers = getFileMatchers(configuration, currentFilename);
        if (!fileMatchers.isEmpty()) {
            // iterate thru files
            final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
            projectFileIndex.iterateContent(new ContentIterator() {
                private PsiManager psiManager = PsiManager.getInstance(project);
                public boolean processFile(VirtualFile fileOrDir) {
                    // if not a directory
                    if (!fileOrDir.isDirectory()) {
                        // and not currentFile...
                        if (!currentFilename.equals(fileOrDir.getName()) || !currentFile.getPath().equals(fileOrDir.getPath())) {
                            // iterate thru matchers and test...
                            for (AlternateFileMatcher fileMatcher : fileMatchers) {
                                if (fileMatcher.matches(fileOrDir.getName())) {
                                    PsiFile psiFile = psiManager.findFile(fileOrDir);
                                    if (psiFile != null) {
                                        Map<String, AlternateFileGroup> workMap = module.equals(projectFileIndex.getModuleForFile(fileOrDir)) ? moduleWorkMap : projectWorkMap;
                                        // add to module or project group
                                        String baseFilename = fileMatcher.getBaseFilename(fileOrDir.getName());
                                        String groupId = groupId(baseFilename);
                                        AlternateFileGroup group = workMap.get(groupId);
                                        if (group == null) {
                                            group = new AlternateFileGroup(groupId);
                                            workMap.put(groupId, group);
                                        }
                                        group.addFile(baseFilename, psiFile);
                                    }
                                    break;
                                }
                            }
                        }
                    }
                    return true;
                }
            });
        }

        // put groups into lists and sort (by baseFilename)
        List<AlternateFileGroup> moduleWorkList = new ArrayList<AlternateFileGroup>(moduleWorkMap.values());
        Collections.sort(moduleWorkList);
        List<AlternateFileGroup> projectWorkList = new ArrayList<AlternateFileGroup>(projectWorkMap.values());
        Collections.sort(projectWorkList);

        // Enhancement 5: If (at least) one corresponding file is found in the same module, show only files from module
        List<AlternateFileGroup> result;
        if (configuration.onlyFromModule) {
            // if moduleItems are presented, only moduleItems will be added, else projectItems
            result = !moduleWorkList.isEmpty() ? moduleWorkList : projectWorkList;
        } else {
            // add moduleItems then projectItems
            result = new ArrayList<AlternateFileGroup>(moduleWorkList);
            result.addAll(projectWorkList);
        }

        // move current file's group to top
        String currentGroupId = null;
        for (AlternateFileMatcher fileMatcher : fileMatchers) {
            if (fileMatcher.matches(currentFilename)) {
                currentGroupId = groupId(fileMatcher.getBaseFilename(currentFilename));
                break;
            }
        }
        if (currentGroupId != null && currentGroupId.length() > 0) {
            for (int i = 0, resultSize = result.size(); i < resultSize; i++) {
                AlternateFileGroup fileGroup = result.get(i);
                if (fileGroup.getGroupId().equals(currentGroupId)) {
                    if (i > 0) {
                        result.add(0, result.remove(i));
                    }
                    break;
                }
            }
        }
        // move group with no id to bottom
        for (int i = 0, resultSize = result.size(); i < resultSize; i++) {
            AlternateFileGroup fileGroup = result.get(i);
            if (fileGroup.getGroupId().length() == 0) {
                if (i < result.size() - 1) {
                    result.add(result.remove(i));
                }
                break;
            }
        }

        return result;
    }

    @NotNull
    private static String groupId(@NotNull String baseFilename) {
        // group id is lowecase of basefilename
        return baseFilename.toLowerCase(Locale.ENGLISH);
    }

    /**
     * @return  List with currently active FileMatchers ()
     */
    private List<AlternateFileMatcher> getFileMatchers(AlternateConfiguration configuration, String currentFilename) {
        List<AlternateFileMatcher> result = new ArrayList<AlternateFileMatcher>();
        // genericRegexActive (before freeRegexItems, because generic groups)
        if (configuration.genericRegexActive) {
            AlternateGenericRegexFileMatcher fileMatcher = new AlternateGenericRegexFileMatcher(currentFilename, configuration);
            if (fileMatcher.canProcess()) {
                result.add(fileMatcher);
            }
        }
        // freeRegexItems
        if (configuration.freeRegexActive) {
            AlternateFreeRegexFileMatcher fileMatcher = new AlternateFreeRegexFileMatcher(currentFilename, configuration);
            if (fileMatcher.canProcess()) {
                result.add(fileMatcher);
            }
        }
        return result;
    }
}
